/**
 * 
 */
package org.swing.utility.jai.canvas;

import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Component;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import javax.imageio.ImageIO;

import org.swing.utility.jai.imp.Setting;

/**
 * @author lqnhu
 *
 */
public class Canvas3D extends Canvas implements Setting {
	static int cx, cy;
	static long timeDrag = 0, lastDrag = 0;
	boolean active = true;
	int mode = -1; // -1: concave, 1: convex, 0: flat.

	public static final int BLACK = 0xff000000;
	public static final int WHITE = 0xffffffff;
	public static final int YELLOW = 0xffffff00;
	public static final int DARK_GRAY = 0xff666666;
	public static final int LIGHT_GRAY = 0xffbbbbbb;
	public static final int RED = 0xffff0000;
	public static final int CYAN = 0xff00ffff;
	private BufferedImage mImg;
	private Graphics2D mG;
	private float mStrokeWidth = 1.0f;

	/** Initialize a Concave 3D Canvas */
	public Canvas3D() {
		setBackground(gray);
	}

	public Canvas3D(int width, int height) {
		mImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		mG = (Graphics2D) mImg.getGraphics();
		mG.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
	}

	/** Initalize a Blank 3D Canvas with mode setting */
	public Canvas3D(int _mode) {
		this();
		mode = _mode;
	}

	/** Insets setting */
	public Insets insets() {
		return insets;
	}

	/* Mode Setting */
	public int getMode() {
		return mode;
	}

	public boolean setMode(int _mode) {
		if (mode == _mode)
			return false;
		mode = _mode;
		repaint();
		return true;
	}

	/* Active Setting */
	public boolean getActive() {
		return active;
	}

	public boolean setActive(boolean _active) {
		if (active == _active)
			return false;
		active = _active;
		repaint();
		return true;
	}

	/* Draw 3D Rect Utility Method */
	public void Draw3DRect(Graphics g, int depth, Color color) {
		Draw3DRect(g, 0, 0, size().width, size().height, depth, color);
	}

	static void Draw3DRect(Graphics g, int x, int y, int w, int h, int depth,
			Color color) {
		int i;
		Color LT, RB;
		if (depth > 0) {
			LT = ltGray;
			RB = dkGray;
		} else {
			LT = dkGray;
			RB = ltGray;
			depth = -depth;
		}
		w += x - 1;
		h += y - 1;
		for (i = 0; i < depth; i++) {
			g.setColor(RB);
			g.drawLine(w - i, h - i, w - i, y + i);
			g.drawLine(w - i, h - i, x + i, h - i);
			g.setColor(LT);
			g.drawLine(x + i, y + i, w - i, y + i);
			g.drawLine(x + i, y + i, x + i, h - i);
		}
		if (color != null) {
			w -= x;
			h -= y;
			g.setColor(color);
			g.fillRect(x + i, y + i, w - i - i + 1, h - i - i + 1);
		}
	}

	public void repaint() {
		Graphics g = getGraphics();
		if (g == null)
			return;
		paintAll(g);
		g.dispose();
	}

	/** Call update when respont to a exposure */
	public void paint(Graphics g) {
		update(g);
	}

	/** Respond to a repaint request */
	public void update(Graphics g) {
		if (g == null)
			return;
		int w = size().width - d2;
		int h = size().height - d2;
		if (mode < 0) {
			Draw3DRect(g, -depth, null);
			g.clipRect(depth, depth, w, h);
		} else if (mode > 0) {
			Draw3DRect(g, depth, null);
			g.clipRect(depth, depth, w, h);
		}
	}

	/** Notify Parent Object */
	public void Notify(Event ev, Object arg) {
		Component p = getParent();
		while ((p != null) && !p.action(ev, arg))
			p = p.getParent();
	}

	/** Simulate a Delay Mouse Drag Action */
	public boolean mouseDrag(Event ev, int x, int y) {
		if (ev.key == 0) { /* Orginal Message */
			cx = x;
			cy = y;
			timeDrag = ev.when;
			ev.key = -1;
			if (System.currentTimeMillis() - lastDrag < 100) {
				new Delay(50, ev, this);
			} else {
				delayDrag(ev, cx, cy);
				lastDrag = System.currentTimeMillis();
			}
		} else if (ev.when == timeDrag) {
			delayDrag(ev, cx, cy);
			lastDrag = System.currentTimeMillis();
		}
		return true;
	}

	/** The actual Dealay Mose Drag Action here */
	public boolean delayDrag(Event ev, int x, int y) {
		return true;
	}

	public int getWidth() {
		return mImg.getWidth();
	}

	public int getHeight() {
		return mImg.getHeight();
	}

	public void writeTo(File f) throws IOException {
		ImageIO.write(mImg, "png", f);
	}

	public void writeTo(OutputStream os) throws IOException {
		ImageIO.write(mImg, "png", os);
	}

	public float getStrokeWidth() {
		return mStrokeWidth;
	}

	public void setStrokeWidth(float width) {
		mStrokeWidth = width;
		mG.setStroke(new BasicStroke(width));
	}

	public void setColor(int argb) {
		mG.setColor(new Color(argb, true));
	}

	public void drawLine(int x0, int y0, int x1, int y1) {
		mG.drawLine(x0, y0, x1, y1);
	}

	public void fillRect(int x, int y, int w, int h) {
		mG.fillRect(x, y, w, h);
	}

	public void drawRect(int x, int y, int w, int h) {
		mG.drawRect(x, y, w, h);
	}

	public float getFontHeight() {
		return mG.getFontMetrics().getHeight();
	}

	public float getStringWidth(String string) {
		return mG.getFontMetrics().stringWidth(string);
	}

	public float getAscent() {
		return mG.getFontMetrics().getAscent();
	}

	public float getDescent() {
		return mG.getFontMetrics().getDescent();
	}

	public void drawString(String string, float x, float y) {
		mG.drawString(string, x, y);
	}

	public void translate(float dx, float dy) {
		mG.translate(dx, dy);
	}

	public void rotate(float degrees) {
		mG.rotate(degrees);
	}
}
